﻿using System;
using System.Collections.Generic;
using System.Linq;
using ParserGenerator.Exceptions;
using ParserGenerator.GrammarAnalysis;
using ParserGenerator.GrammarConstruction;

namespace ParserGenerator.ParserConstruction
{
    [Serializable]
    public sealed class ParserAutomatonState
    {
        public int Number { get; }
        public ParserAutomatonStateTable Table { get; }
        public GrammarSymbol Symbol { get; }
        public ParserAutomatonItemSet Core { get; }
        public ParserAutomatonTransitions Transitions { get; }
        public ParserAutomatonReductions Reductions { get; }
        public ParserAutomatonStateGroup Group { get; }
        public ParserAutomatonState LR0Isocore => Group.LR0Isocore;
        public ParserAutomatonStatePredecessors Predecessors => Table.Predecessors[this];
        public ParserAutomatonInadequacyNodes InadequacyNodes => Table.InadequacyNodes[this];
        public bool IsConsistent { get; private set; }
        public bool RecomputedAsSuccessor { get; internal set; }
        public bool NoShifts => Transitions.Values.All(x => x.IsGoto);

        internal ParserAutomatonState(ParserAutomatonStateTable table, GrammarSymbol symbol, ParserAutomatonItemSet core)
        {
            if (table == null)
                throw new ArgumentNullException(nameof(table));
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            if (core == null)
                throw new ArgumentNullException(nameof(core));
            Number = table.Count;
            Table = table;
            Symbol = symbol;
            Core = core;
            Transitions = new ParserAutomatonTransitions(this);
            Reductions = new ParserAutomatonReductions(this);
            Group = new ParserAutomatonStateGroup(this);
        }

        internal ParserAutomatonState(ParserAutomatonState other)
        {
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            Number = other.Table.Count;
            Table = other.Table;
            Symbol = other.Symbol;
            Core = other.Core;
            Transitions = new ParserAutomatonTransitions(this, other.Transitions);
            Reductions = new ParserAutomatonReductions(this, other.Reductions);
            Group = other.Group;
            Group.Add(this);
            IsConsistent = other.IsConsistent;
            RecomputedAsSuccessor = true;
        }

        internal Dictionary<GrammarSymbol, ParserAutomatonItemSet> Partition()
        {
            var dict = new Dictionary<GrammarSymbol, ParserAutomatonItemSet>();
            ParserAutomatonItemSet closure = Core.ComputeClosure(true);
            foreach (ParserAutomatonItem item in closure)
            {
                if (item.IsReduce)
                    AddReduction(item.Production);
                else
                {
                    ParserAutomatonItemSet group;
                    if (!dict.TryGetValue(item.Symbol, out group))
                    {
                        group = new ParserAutomatonItemSet();
                        dict.Add(item.Symbol, group);
                    }
                    group.Add(item.Successor);
                }
            }
            return dict;
        }

        internal void AddTransition(GrammarSymbol symbol, ParserAutomatonState target)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            if (target == null)
                throw new ArgumentNullException(nameof(target));
            Transitions.Add(symbol, target);
        }

        internal void AddReduction(GrammarProduction production)
        {
            if (production == null)
                throw new ArgumentNullException(nameof(production));
            Reductions.Add(production);
        }

        internal void UpdateTransition(GrammarSymbol symbol, ParserAutomatonState target)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            if (target == null)
                throw new ArgumentNullException(nameof(target));
            ParserAutomatonState oldTarget = Transitions[symbol];
            if (target == oldTarget)
                return;
            Transitions.Update(symbol, target);
        }

        internal void CheckConsistency(bool useDefaultReductions)
        {
            IsConsistent = Reductions.Count == 0 ||
                           Reductions.Count == 1 &&
                           NoShifts &&
                           useDefaultReductions;
        }

        public HashSet<GrammarTerminalSymbol> GetShiftTokens()
        {
            var tokens = new HashSet<GrammarTerminalSymbol>();
            foreach (var t in Transitions)
                if (t.Key.IsTerminal)
                    tokens.Add(t.Key.AsTerminal);
            return tokens;
        }

        public HashSet<GrammarTerminalSymbol> GetConflictedTokens()
        {
            HashSet<GrammarTerminalSymbol> shiftTokens = GetShiftTokens();
            return GetConflictedTokens(shiftTokens);
        }

        public HashSet<GrammarTerminalSymbol> GetConflictedTokens(HashSet<GrammarTerminalSymbol> shiftTokens)
        {
            if (shiftTokens == null)
                throw new ArgumentNullException(nameof(shiftTokens));
            var conf = new HashSet<GrammarTerminalSymbol>();
            ParserAutomatonReductionLookaheadSets lookaheadSets;
            if (!Table.ReductionLookaheadSets.TryGetValue(this, out lookaheadSets))
                return conf;
            var used = new HashSet<GrammarTerminalSymbol>(shiftTokens);
            foreach (ParserAutomatonReduction reduce in Reductions.Values)
            {
                var lookaheadSet = lookaheadSets[reduce.Production];
                var confDelta = new HashSet<GrammarTerminalSymbol>(lookaheadSet);
                confDelta.IntersectWith(used);
                conf.UnionWith(confDelta);
                used.UnionWith(lookaheadSet);
            }
            if (Table.LexicalResolutionCache != null)
                Table.LexicalResolutionCache.ExtendConflictSet(conf, used);
            return conf;
        }

        public ParserAutomatonTransition GetTransition(GrammarSymbol symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            return Transitions.GetTransition(symbol);
        }

        public ParserAutomatonReduction GetReduction(GrammarProduction production)
        {
            if (production == null)
                throw new ArgumentNullException(nameof(production));
            return Reductions.GetReduction(production);
        }

        private ParserAutomatonAnnotationNodes GetAnnotationNodes()
        {
            if (Table.AnnotationNodes == null)
                return null;
            ParserAutomatonAnnotationNodes nodes;
            Table.AnnotationNodes.TryGetValue(LR0Isocore, out nodes);
            return nodes;
        }

        internal ParserAutomatonItemLookaheadSets ComputeLookaheadFilter()
        {
            if (Table.AutomatonType == ParserAutomatonType.LR)
                return null;
            var filter = new ParserAutomatonItemLookaheadSets(this);
            ParserAutomatonAnnotationNodes nodes = GetAnnotationNodes();
            if (nodes == null)
                return filter;
            foreach (ParserAutomatonAnnotationNode node in nodes)
            {
                var token = node.InadequacyNode.ConflictedToken;
                foreach (var c in node.Contributions)
                    if (!node.IsAlwaysContribution(c.Key))
                        foreach (ParserAutomatonItem item in node.Contributions[c.Key])
                            filter.AddValueItem(item, token, this);
            }
            return filter;
        }

        private bool LookaheadSetsMatch(ParserAutomatonItemLookaheadSets sets)
        {
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            var lookaheadSets = Table.ItemLookaheadSetsCache.GetLookaheadSets(this);
            foreach (ParserAutomatonItem item in Core)
            {
                ParserAutomatonItemLookaheadSet lookaheadSet;
                if (lookaheadSets.TryGetValue(item, out lookaheadSet) && lookaheadSet.Count > 0)
                {
                    ParserAutomatonItemLookaheadSet set;
                    if (!sets.TryGetValue(item, out set) || !lookaheadSet.SetEquals(set))
                        return false;
                }
                else
                {
                    ParserAutomatonItemLookaheadSet set;
                    if (sets.TryGetValue(item, out set) && set.Count > 0)
                        return false;
                }
            }
            return true;
        }

        private ParserAutomatonDominantContributions ComputeDominantContributions(ParserAutomatonItemLookaheadSets sets)
        {
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            ParserAutomatonAnnotationNodes nodes = GetAnnotationNodes();
            if (nodes == null)
                return null;
            var dominantContributions = new ParserAutomatonDominantContributions();
            foreach (ParserAutomatonAnnotationNode node in nodes)
            {
                var contrib = node.ComputeDominantContribution(sets, false);
                dominantContributions.Add(contrib);
            }
            return dominantContributions;
        }

        private bool DominantContributionsMatch(ParserAutomatonDominantContributions dominantContributions)
        {
            ParserAutomatonAnnotationNodes nodes = GetAnnotationNodes();
            if (nodes == null)
                return true;
            if (dominantContributions == null)
                throw new ArgumentNullException(nameof(dominantContributions));
            int i = 0;
            var lookaheadSets = Table.ItemLookaheadSetsCache.GetLookaheadSets(this);
            foreach (ParserAutomatonAnnotationNode node in nodes)
            {
                if (dominantContributions[i] != null)
                {
                    var contrib = node.ComputeDominantContribution(lookaheadSets, false);
                    if (contrib != null && contrib != dominantContributions[i])
                        return false;
                }
                i++;
            }
            return true;
        }

        private bool TokenResolutionsMatch(ParserAutomatonItemLookaheadSets sets)
        {
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            //NotImplementedErrorException e = new NotImplementedErrorException(SR.PSLRNotImplemented);
            //throw e;
            var lookaheadSets = Table.ItemLookaheadSetsCache.GetLookaheadSets(this);
            if (lookaheadSets == null)
                return true;
            return Table.LexicalResolutionCache.IsCompatible(lookaheadSets, sets);
        }

        private ParserAutomatonState FindMergableIsocore(ParserAutomatonItemLookaheadSets sets)
        {
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            var dominantContributions = LR0Isocore.ComputeDominantContributions(sets);
            foreach (ParserAutomatonState isocore in Group)
            {
                if (!isocore.RecomputedAsSuccessor)
                    return isocore;
                if (Table.AutomatonType == ParserAutomatonType.LR)
                {
                    if (isocore.LookaheadSetsMatch(sets))
                        return isocore;
                }
                else
                {
                    if (isocore.DominantContributionsMatch(dominantContributions))
                    {
                        if (Table.AutomatonType != ParserAutomatonType.PSLR)
                            return isocore;
                        if (isocore.TokenResolutionsMatch(sets))
                            return isocore;
                    }
                }
            }
            return null;
        }

        private bool MergeIsocoreLookaheads(ParserAutomatonItemLookaheadSets sets)
        {
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            if (!sets.CheckHaveLookheads())
                return false;
            bool newLookaheads = false;
            foreach (ParserAutomatonItem item in Core)
            {
                ParserAutomatonItemLookaheadSet set;
                if (sets.TryGetValue(item, out set))
                {
                    var lookaheadSet = Table.ItemLookaheadSetsCache.GetLookaheadSet(this, item);
                    newLookaheads |= lookaheadSet.MergeWith(set);
                }
            }
            return newLookaheads;
        }

        private void MergeIsocore(ParserAutomatonItemLookaheadSets sets)
        {
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            bool newLookaheads = MergeIsocoreLookaheads(sets);
            if (!RecomputedAsSuccessor)
                RecomputedAsSuccessor = true;
            else if (newLookaheads)
            {
                // Copy transition keys so that transition dictionary can be updated.
                GrammarSymbol[] symbols = Transitions.Keys.ToArray();
                foreach (GrammarSymbol symbol in symbols)
                {
                    ParserAutomatonState target = Transitions[symbol];
                    if (!target.RecomputedAsSuccessor)
                        break;
                    var lookaheadFilter = target.ComputeLookaheadFilter();
                    var lookaheadSets = Table.ItemLookaheadSetsCache.ComputeIelrLookaheadSets(this, target, lookaheadFilter);
                    ParserAutomatonState t = target.ComputeIelrState(lookaheadSets);
                    UpdateTransition(symbol, t);
                }
            }
        }

        internal ParserAutomatonState ComputeIelrState(ParserAutomatonItemLookaheadSets sets)
        {
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            ParserAutomatonState isocore = FindMergableIsocore(sets);
            if (isocore == null)
                isocore = Table.CreateNewIsocore(LR0Isocore, sets);
            else
                isocore.MergeIsocore(sets);
            return isocore;
        }

        public sealed override string ToString()
        {
            return Number.ToString();
        }
    }
}
